import java.net.DatagramPacket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.UUID;

public class Server
{
    private HashMap<String, ClientStatus> table = new HashMap<String, ClientStatus>();
    private HashMap<String, ArrayList<ChatMsg>> offlineMsg = new HashMap<String, ArrayList<ChatMsg>>();

    private int listenPort;

    private static final int bufferSize = 10000;

    public Server(int listenPort)
    {
        this.listenPort = listenPort;
    }

    private void announce()
    {
        UDPWrapper sender = new UDPWrapper();
        for (String name : table.keySet())
        {
            ClientStatus status = table.get(name);
            if (status.isOnline)
            {
                Packet packet = new Packet(Packet.PacketType.ClientTable, table);
                sender.send(packet, status.ipAddress, status.recvPort);
            }
        }
        sender.close();
    }

    private void logoff(LogoffRequest request)
    {
        ClientStatus status = table.get(request.name);

        if (status != null)
        {
            status.isOnline = false;
            sendACK(request.name, request.id);
        }
    }

    private void register(String name, String ip, int recvPort)
    {
        table.put(name, new ClientStatus(ip, recvPort, true));
    }

    public void run()
    {
        UDPWrapper socket = null;
        try
        {
            socket = new UDPWrapper(listenPort);
        }
        catch (SocketException e)
        {
            System.out.println(">>> [Receive port in use. Exiting...]");
            System.exit(1);
        }

        Utility.debugInfo("Socket opened on: " + listenPort);

        while (true)
        {
            byte[] buffer = new byte[bufferSize];
            DatagramPacket rawPacket = new DatagramPacket(buffer, bufferSize);
            socket.receive(rawPacket);

            Packet packet = (Packet) Utility.deserializeObj(rawPacket.getData());
            switch (packet.type)
            {
            case RegRequest:
            {
                RegRequest status = (RegRequest) packet.data;
                String name = status.nickname;
                register(name, Utility.buildIPString(rawPacket.getAddress()), status.recvPort);
                ArrayList<ChatMsg> msgs = offlineMsg.get(name);
                if (msgs != null && msgs.size() > 0)
                    sendOfflineMsg(name, msgs);
                sendACK(status.nickname, status.uniqueID);
                announce();
            }
                break;
            case ChatMsg:
            {
                ChatMsg msg = (ChatMsg) packet.data;

                ClientStatus recipientStatus = table.get(msg.recipient);
                if (recipientStatus != null)
                {
                    if (recipientStatus.isOnline)
                    {
                        recipientStatus.isOnline = false;
                        announce();
                    }

                    storeMsg(msg.recipient, msg);
                    sendACK(msg.sender, msg.uniqueID);
                }
                break;
            }
            case LogoffRequest:
            {
                LogoffRequest request = (LogoffRequest) packet.data;
                logoff(request);
                announce();
                break;
            }
            case LoginRequest:
                break;
            }
        }
    }

    void sendACK(String name, UUID id)
    {
        UDPWrapper sender = new UDPWrapper();
        ClientStatus status = table.get(name);
        if (status != null)
            sender.send(new Packet(Packet.PacketType.ACK, id), status.ipAddress, status.recvPort);
        sender.close();
    }

    private void sendOfflineMsg(String name, ArrayList<ChatMsg> msgs)
    {
        UDPWrapper sender = new UDPWrapper();
        ClientStatus status = table.get(name);
        sender.send(new Packet(Packet.PacketType.OfflineMsg, msgs), status.ipAddress, status.recvPort);
        msgs.clear();
        sender.close();
    }

    private void storeMsg(String name, ChatMsg msg)
    {
        if (offlineMsg.get(name) == null)
            offlineMsg.put(name, new ArrayList<ChatMsg>());

        ArrayList<ChatMsg> msgs = offlineMsg.get(name);
        msgs.add(msg);

        Utility.debugInfo("Message stored");
    }
}
